# VTKInteractor.py

import os, wx
import vtk

from model import *

from wx.lib.pubsub import Publisher


# Timer class used by VTK
class EventTimer(wx.Timer):
	
	# constructor
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def __init__(self, iren):
		'''
			Construct a new EventTimer
			Used to time events
			
			@param iren: the RenderWindowInteractor to bind the TimerEvent to
		'''
		wx.Timer.__init__(self)
		self.iren = iren
	
	
	# Trigger the Timer event on the interactor
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def Notify(self):
		'''
			Notify the RenderWindowInteractor about the elapsed timer and trigger a timer event
		'''
		self.iren.TimerEvent()


# Own implementation of VTK RenderWindowInteractor
class VTKInteractor (vtk.vtkGenericRenderWindowInteractor) :

	# Class variables
	NO_BUTTONS = 0
	LEFT_BUTTON = 1
	MIDDLE_BUTTON = 2
	RIGHT_BUTTON = 4
	ALL_BUTTONS = LEFT_BUTTON | MIDDLE_BUTTON | RIGHT_BUTTON
	

	# constructor
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def __init__(self, model, vtkcomp):
		'''
			Construct a new VTK Interactor (baed on the vtkGenericRenderWindowInteractor)
			
			@param model: a reference to the model
			@param vtkcomp: a reference to the VTK Component (or actually VTK View)
		'''
		# store references
		self._model = model
		self._vtkcomp = vtkcomp
		self._renderer = vtkcomp.GetRenderer()
		self._renderwindow = vtkcomp.GetRenderWindow()
		
		self.SetRenderWindow(self._renderwindow)
		
		# picked properties
		self._picker = vtk.vtkPropPicker()
		self._lastPicked = None
		self._lastHighlighted = None
		self._lastHighlightedProp = None
		
		# mouse event variables
		self._buttonsPressed = self.NO_BUTTONS
		self._mouseMoved = False
		#self._mouseScroll = 0
		
		self._rotateEnabled = True
		self._panEnabled = True
		self._zoomEnabled = True
		
		# bind VTK Component events
		self._vtkcomp.Bind(wx.EVT_CHAR, self.KeyDown) # NOTE: use EVT_CHAR instead of EVT_KEY_DOWN
		self._vtkcomp.Bind(wx.EVT_KEY_UP, self.KeyUp)
		
		self._vtkcomp.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
		self._vtkcomp.Bind(wx.EVT_MIDDLE_DOWN, self.MouseDown)
		self._vtkcomp.Bind(wx.EVT_RIGHT_DOWN, self.MouseDown)

		self._vtkcomp.Bind(wx.EVT_LEFT_UP, self.MouseUp)
		self._vtkcomp.Bind(wx.EVT_MIDDLE_UP, self.MouseUp)
		self._vtkcomp.Bind(wx.EVT_RIGHT_UP, self.MouseUp)

		self._vtkcomp.Bind(wx.EVT_MOUSEWHEEL, self.MouseWheel)
		
		self._vtkcomp.Bind(wx.EVT_MOTION, self.MouseMove)
		
		self._vtkcomp.Bind(wx.EVT_ENTER_WINDOW, self.MouseEnter)
		self._vtkcomp.Bind(wx.EVT_LEAVE_WINDOW, self.MouseLeave)
		
		# set default interactor style to trackball camera
		self.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
		#self.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
		
		# set mouse wheel zoom factor
		self.GetInteractorStyle().SetMouseWheelMotionFactor(1.1)
		
		# Listen for timer events
		self.AddObserver("CreateTimerEvent", self.CreateTimer)
		self.AddObserver("DestroyTimerEvent", self.DestroyTimer)
		
		Publisher.subscribe(self._GeomPicked, "GeomPicked")
	
		
	##########
	# VTK Component Key Events and Mouse Events
	#########
	
	# Triggered by key pressed events
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def KeyDown(self, event):
		'''
			Triggered by key down events
			
			@param event: the key event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# handle key event on our own
		if event.GetKeyCode() == ord('a'):
			#self._vtkcomp.SetAxesOrigin(not self._vtkcomp.AxesOriginEnabled())
			pass
		elif event.GetKeyCode() == ord('r'):
			self._vtkcomp.ResetCamera()
		elif event.GetKeyCode() == ord('s'):
			#print "Making screenshot!"
			w2i = vtk.vtkWindowToImageFilter()
			writer = vtk.vtkPNGWriter()
			w2i.SetInput(self._renderwindow)
			w2i.Update()
			writer.SetInputConnection(w2i.GetOutputPort())
			writer.SetFileName("screenshot.png")
			self._renderwindow.Render()
			writer.Write()	
			#print "Done!"
		elif event.GetKeyCode() == ord('c'):
			#clone the picked geom
			if self._lastPicked != None:
				self._model.Clone(self._lastPicked)
		elif event.GetKeyCode() == ord('o'):
			self._vtkcomp.SetOrthogonalView()
		elif event.GetKeyCode() == ord('p'):
			self._vtkcomp.SetPerspectiveView()
		elif event.GetKeyCode() == wx.WXK_DELETE and self._lastPicked != None:
			self._model.RemoveGeom(self._lastPicked)
			#self._model.RemoveFromPath(self._lastPicked)
			#self._model.RemoveFromTumor(self._lastPicked)
			self._lastPicked = None
			#Publisher.sendMessage("GeomPicked", self._lastPicked)
		#elif event.GetKeyCode() == ord('g'):
		#	self._vtkcomp.EnableOrientationWidget()

			
	# Triggered by key released events
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def KeyUp(self, event):
		'''
			Triggered by key up events
			
			@param event: the key event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# handle key event on our own
		"""
		# trigger key event
		x, y = self.GetEventPosition()
		key = chr(0)
		if event.GetKeyCode() < 256:
			key = chr(event.GetKeyCode())
			
		self.SetEventInformation(x, y, event.ControlDown(), event.ShiftDown(), key, 0, None)
		self.KeyReleaseEvent()
		"""
		
	
	# Triggered by mouse cursor entering VTK Component
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def MouseEnter(self, event):
		'''
			Triggered by mouse enter events
			
			@param event: the mouse enter event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# set motion flag
		#self._mouseMoved = False
		
		# trigger mouse event
		self.SetEventInformationFlipY(event.GetX(), event.GetY(), event.ControlDown(), event.ShiftDown(), chr(0), 0, None)
		
		# trigger event
		self.EnterEvent()
		
	
	# Triggered by mouse cursor leaving VTK Component
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def MouseLeave(self, event):
		'''
			Triggered by mouse leave events
			
			@param event: the mouse leave event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# set motion flag
		#self._mouseMoved = False
		
		# trigger mouse event
		self.SetEventInformationFlipY(event.GetX(), event.GetY(), event.ControlDown(), event.ShiftDown(), chr(0), 0, None)
		
		# trigger event
		self.LeaveEvent()
		
		#disable highlight
		#print "Leave window"
		if self._lastHighlighted != None:
			
			# undo highlight
			self._lastHighlighted.SetColor(self._lastHighlightedProp[0], self._lastHighlightedProp[1], self._lastHighlightedProp[2])
			self._lastHighlighted = None
			self._lastHighlightedProp = None
			Publisher.sendMessage("GeomHighlighted", self._lastHighlighted)
			
	
	# Triggered by mouse button pressed events
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def MouseDown(self, event):
		'''
			Triggered by mouse down events
			
			@param event: the mouse down event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# set motion flag
		self._mouseMoved = False
		
		# trigger mouse event
		self.SetEventInformationFlipY(event.GetX(), event.GetY(), event.ControlDown(), event.ShiftDown(), chr(0), 0, None)
		
		# select mouse button
		if event.LeftDown():
			self._buttonsPressed = self._buttonsPressed | self.LEFT_BUTTON
			self.LeftButtonPressEvent()
		
		if event.MiddleDown():
			self._buttonsPressed = self._buttonsPressed | self.MIDDLE_BUTTON
			self.MiddleButtonPressEvent()
			
		if event.RightDown():
			self._buttonsPressed = self._buttonsPressed | self.RIGHT_BUTTON
			self.RightButtonPressEvent()
		
		# highlight geom
		self.HighlightGeom(event.GetX(), event.GetY())
	
	
	# Triggered by mouse button release events
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def MouseUp(self, event):
		'''
			Triggered by mouse up events
			
			@param event: the mouse up event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# trigger mouse event
		self.SetEventInformationFlipY(event.GetX(), event.GetY(), event.ControlDown(), event.ShiftDown(), chr(0), 0, None)
		
		# select mouse button
		if event.LeftUp():
			
			# RSLV: put selection event here?
			if not self._mouseMoved and self.IsButtonDown(self.LEFT_BUTTON): # or was down :D
				
				# get geom
				geom = self.PickGeom(event.GetX(), event.GetY())
				Publisher.sendMessage("GeomPicked", geom)
				
			else:
			
				# TODO: trigger mouse move event?
				# for highlighting...
				pass
			
			# release left mouse button
			self._buttonsPressed = self._buttonsPressed & ~self.LEFT_BUTTON
			self.LeftButtonReleaseEvent()
					
		if event.MiddleUp():
		
			# release middle mouse button
			self._buttonsPressed = self._buttonsPressed & ~self.MIDDLE_BUTTON
			self.MiddleButtonReleaseEvent()
		
		if event.RightUp():
		
			# release right mouse button
			self._buttonsPressed = self._buttonsPressed & ~self.RIGHT_BUTTON
			self.RightButtonReleaseEvent()
		
		# reset mouse motion
		self._mouseMoved = False
		
		# highlight geom
		self.HighlightGeom(event.GetX(), event.GetY())
	
	
	# Returns if button is pressed by the user
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def IsButtonDown(self, button):
		'''
			Check if a button or set of buttons is pressed down by the user
			
			@param button: the button or set of buttons
			
			@note: this is a private method and should not be called by the user
		'''
		return self._buttonsPressed & button # NOTE: returns an integer, not a boolean value!!
	
	
	# Triggered on mouse wheel events
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def MouseWheel(self, event):
		'''
			Triggered by mouse wheel events
			
			@param event: the mouse wheel event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# trigger mouse event
		if self._zoomEnabled:
			self.SetEventInformationFlipY(event.GetX(), event.GetY(), event.ControlDown(), event.ShiftDown(), chr(0), 0, None)
		
			# select wheel direction
			if event.GetWheelRotation() > 0:
				self.MouseWheelForwardEvent()
		
			else:
				self.MouseWheelBackwardEvent()
		
		# highlight geom
		self.HighlightGeom(event.GetX(), event.GetY())
	
	
	# Triggered on mouse movement events
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def MouseMove(self, event):
		'''
			Triggered by mouse move events
			
			@param event: the mouse move event
			
			@note: this is a private method and should not be called by the user
		'''
		# forward event
		event.Skip()
		
		# set motion flag
		self._mouseMoved = True
		
		# enable functionality
		if (self._rotateEnabled and self.IsButtonDown(self.LEFT_BUTTON)) or (self._panEnabled and self.IsButtonDown(self.MIDDLE_BUTTON)) or (self._zoomEnabled and self.IsButtonDown(self.RIGHT_BUTTON)):
			# trigger mouse event
			self.SetEventInformationFlipY(event.GetX(), event.GetY(), event.ControlDown(), event.ShiftDown(), chr(0), 0, None)
			self.MouseMoveEvent()

		# RSLV: put highlight here?
		# NOTE: check for button may be removed for real-time picking during dragging
		if not self.IsButtonDown(self.ALL_BUTTONS):
			
			# highlight geom
			self.HighlightGeom(event.GetX(), event.GetY())
	
	
	# Pick a geom at screen coordinates
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	a geom, or none
	def PickGeom(self, x, y):
		'''
			Pick a geom from the model at view coordinates (x,y)
			
			@param x: the x coordinate
			@param x: the y coordinate
		'''
		# create list of mutable geoms
		lst = self._model.GetMutables()
		
		mutables = vtk.vtkPropCollection()
		for i in range(len(lst)):
			mutables.AddItem(lst[i].GetActor())			
		
		# pick geom
		self._picker.PickProp(x, self.GetSize()[1] - y, self._vtkcomp.GetRenderer(), mutables)
		#self._picker.PickProp(x, self.GetSize()[1] - y, self._vtkcomp.GetRenderer()) # NOTE: flip y coordinate
		actor = self._picker.GetActor()
		
		# get geom
		geom = None
		if actor != None and hasattr(actor, "GetGeom"):
			
			# get geom from actor
			geom = actor.GetGeom()
		
		# return the geom
		return geom
	
	
	# Highlight geom at screen coordinates
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def HighlightGeom(self, x, y):
		'''
			Highlight a geom from the model at view coordinates (x,y)
			
			@param x: the x coordinate
			@param x: the y coordinate
		'''
		# pick geom
		geom = self.PickGeom(x, y)
		
		# reset properties of last highlighted geom
		if self._lastHighlighted != None and self._lastHighlighted != geom:
			
			# undo highlight
			self._lastHighlighted.SetColor(self._lastHighlightedProp[0], self._lastHighlightedProp[1], self._lastHighlightedProp[2])
			self._lastHighlighted = None
			self._lastHighlightedProp = None
		
		# set properties of new highllighted geom
		if geom != None and self._lastHighlighted != geom:
			
			# backup color of geom
			self._lastHighlighted = geom
			self._lastHighlightedProp = self._lastHighlighted.GetColor()
			
			# set highlighted color
			self._lastHighlighted.SetColor(1, 1, 1)
			Publisher.sendMessage("GeomHighlighted", self._lastHighlighted)
			
	
	# Hide the mouse cursor
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def HideCursor(self):
		'''
			Hide the mouse cursor
		'''
		# set blank cursor for VTK Component
		self._vtkcomp.SetCursor(wx.CURSOR_BLANK)
		
	
	# Show the default arrow mouse cursor
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def ShowCursor(self):
		'''
			Show the mouse cursor
		'''
		# set default cursor for VTK Component
		self._vtkcomp.SetCursor(wx.CURSOR_ARROW)


	##########
	# VTK Timer Events
	#########
	
	# Create a new timer object and start the timer
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def CreateTimer(self, obj, evt):
		'''
			Create a new EventTimer object
			
			@note: this is a private method and should not be called by the user
		'''
		# create new timer
		self._timer = EventTimer(self)
		self._timer.Start(10, True)


	# Destroy the timer
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def DestroyTimer(self, obj, evt):
		'''
			Destroy the EventTimer object
			
			@note: this is a private method and should not be called by the user
		'''
		# stop timer
		return 1
	
	
	##########
	# Publisher / Subscriber Events
	#########
	
	# geom picked in some render window
	# -----------
	# dscr:	-
	# req:	-
	# ass:	-
	# ret:	n.a.
	def _GeomPicked(self, message):
		'''
			Listens to GeomPicked events
			
			@param message: event message
			
			@note: this is a private method and should not be called by the user
		'''
		if message != None:
			# get geom
			geom = message.data
		else:
			geom = None
		
		# disable box widget on previous picked geom
		if self._lastPicked != None and self._lastPicked != geom:
			self._lastPicked.DisableBoxWidgets()
			self._lastPicked = None
			#Publisher.sendMessage("GeomPicked", self._lastPicked)
		
		# select geom
		if geom != None and self._lastPicked != geom:
			
			# enable box widget on last picked geom
			self._lastPicked = geom
			self._lastPicked.EnableBoxWidgets()
			#Publisher.sendMessage("GeomPicked", self._lastPicked)
			
		#self._lastPicked = geom
